home *** CD-ROM | disk | FTP | other *** search
/ Delphi Informant Complete 1995 - 2000 / Delphi Informant Complete 1995 to 2000.iso / Delphi Informant Magazine Complete Works SOURCE CODE 1998.rar / 1998 / Jun / di9806sb / sccomps.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1998-02-06  |  13.8 KB  |  293 lines

  1. unit Sccomps;
  2. {******************************************************************************}
  3. {*** ScComps.PAS             **************************************************}
  4. {*** by Stephen R. Broadwell **************************************************}
  5. {*** copyright (c) 1997      **************************************************}
  6. {******************************************************************************}
  7. {*** This unit defines the component TSeatChecker, a license-enforcing      ***}
  8. {*** component for database applications.  By using TSeatChecker in your    ***}
  9. {*** code, you can enforce your licensing agreement by limiting either the  ***}
  10. {*** total number of concurrent users of your software, or the number of    ***}
  11. {*** seats.                                                                 ***}
  12. {******************************************************************************}
  13. {*** TSeatChecker works by making use of a lurch table to keep track of     ***}
  14. {*** who is using the software.  See the associated documentation for more  ***}
  15. {*** details on creating and using a lurch table.                           ***}
  16. {******************************************************************************}
  17. {******************************************************************************}
  18. {*** Please direct any and all questions/comments to:                       ***}
  19. {*** sbroadwell@bridge-way.com                                              ***}
  20. {******************************************************************************}
  21.  
  22. interface
  23.  
  24. uses
  25.   SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  26.   Forms, Dialogs, DB, DBTables;
  27.  
  28. const
  29.   kLurchTableName = 'LURCH.DB';
  30. { This is the name of the lurch table.  You may want to change it for security }
  31. { reasons.                                                                     }
  32.   kMaxSeats = 3;
  33. { The maximum number of seats.  This is only useful for applications that are  }
  34. { licensed by concurrent use.  You may want to make this a property, or get it }
  35. { from a DLL or an ActiveX or OLE control, to make it easier to adjust the     }
  36. { number of seats at a customer site (i.e. drop in a new DLL to increase the   }
  37. { licensed number of concurrent users.                                         }
  38.  
  39. type
  40.   ESeatCheckError = class(Exception);
  41. { This is a general exception for all errors within this unit.                 }
  42.   TStatus = (sSitting, sStanding);
  43. { This type defines the status of the application, whether it is occupying one }
  44. { of the licensed seats or not.                                                }
  45.   TMethod = (mByUser, mByTotal);
  46. { TMethod defines the two methods of software licensing -- by user (seat-      }
  47. { based) or by total number of users (concurrent use-based).                   }
  48.  
  49.   TSeatChecker = class(TComponent)
  50.   private
  51.     { Private declarations }
  52.     fDBName : TFileName;
  53.     fStatus : TStatus;
  54.     fMethod : TMethod;
  55.     fUserName : String;
  56.     fLurchTbl : TTable;
  57.     procedure SetDBName(value : TFileName);
  58.     procedure SetStatus(value : TStatus);
  59.     procedure SetMethod(value : TMethod);
  60.   protected
  61.     { Protected declarations }
  62.     procedure EmptyLurchTable; virtual;
  63.   { The EmptyLurchTable method empties out all of the 'phantom' users in the   }
  64.   { table.                                                                     }
  65.   public
  66.     { Public declarations }
  67.     constructor Create(AOwner : TComponent); override;
  68.     procedure SitDown;
  69.     procedure StandUp;
  70.   { The above two methods are the business end of TSeatChecker.  They control  }
  71.   { the occupying and releasing of license seats by the application.           }
  72.   published
  73.     { Published declarations }
  74.     property DatabaseName : TFileName read fDBName write SetDBName;
  75.   { DatabaseName is the name of the application database.  It is important to  }
  76.   { set this to the database being used by the application, otherwise the      }
  77.   { whole purpose of TSeatChecker is defeated.                                 }
  78.     property Status : TStatus read fStatus write SetStatus;
  79.   { Status indicates whether the application holds a seat or not.              }
  80.     property UserName : String read fUserName write fUserName;
  81.   { Username is the name of the user checking for a seat.                      }
  82.     property Method : TMethod read fMethod write SetMethod;
  83.   { Method is the method of licensing being used, either seat-based or         }
  84.   { concurrent-use based.                                                      }
  85.   end;
  86.  
  87. procedure Register;
  88.  
  89. implementation
  90.  
  91. {$IFDEF WIN32}
  92.   {$R SCCOMPS.D32}
  93. {$ELSE}
  94.   {$R SCCOMPS.D16}
  95. {$ENDIF}
  96.  
  97. procedure Register;
  98. begin
  99.   RegisterComponents('SeatCheck', [TSeatChecker]);
  100. end;
  101.  
  102. { TSeatChecker }
  103.  
  104. constructor TSeatChecker.Create(AOwner : TComponent);
  105. begin
  106.   { begin by calling the inherited constructor }
  107.   inherited Create(AOwner);
  108.   { create the lurch TTable with Self as the owner -- this way we don't have to
  109.     worry about destroying it. }
  110.   fLurchTbl := TTable.Create(Self);
  111.   { make sure we're using the right index (important during the FindKey
  112.     operation in the SitDown method). }
  113.   fLurchTbl.IndexName := 'Username';
  114.   { initialize Status to be standing (i.e. not occupying a seat). }
  115.   fStatus := sStanding;
  116. end;
  117.  
  118. procedure TSeatChecker.SetDBName(value : TFileName);
  119. begin
  120.   { This is your basic property access method.  You may wish to add some extra
  121.     functionality to it, such as checking to see that a lurch table exists in
  122.     the specified database, and creating one if it does not. }
  123.   fDBName := value;
  124. end;
  125.  
  126. procedure TSeatChecker.SetStatus(value : TStatus);
  127. begin
  128.   { Depending on the value of Value, call the SitDown or the StandUp method.  I
  129.     do this so that I can test my application in design-mode, i.e. if I want
  130.     to reserve a seat, I just change the value in the Object Inspector.  This
  131.     is the beauty of component-oriented programming. }
  132.   case value of
  133.     sSitting : SitDown;
  134.     sStanding : StandUp;
  135.   end;
  136. end;
  137.  
  138. procedure TSeatChecker.SetMethod(value : TMethod);
  139. begin
  140.   { Make sure that a seat is not being held first. }
  141.   if fStatus = sSitting then raise ESeatCheckError.Create('Cannot perform this action while holding a seat.');
  142.   fMethod := value;
  143. end;
  144.  
  145. procedure TSeatChecker.EmptyLurchTable;
  146. { This method is key to the whole trick.  Every time a user logs on to the
  147.   main application, TSeatChecker creates a record in the lurch table for the
  148.   user and holds it in edit state.  As long as the record remains in this state,
  149.   it cannot be deleted.  This is true regardless of whether it is being locked
  150.   by an application on another pc, or by one on the same pc.
  151.   If the user quits the application without giving up the seat (e.g. their pc
  152.   is rebooted, or the application crashes), then the record is no longer in
  153.   edit state, but it is still present in the table.  This is called a 'phantom'
  154.   user.  Therefore, before attempting to reserve a seat, TSeatChecker runs
  155.   through the lurch table and attempts to delete every record.  'Phantom'
  156.   records will be deleted, but actual occupied seats will not.  Once the method
  157.   is done, the only records remaining correspond to actual seats. }
  158. var i : integer;
  159. begin
  160.   { check to see that the table is not empty }
  161.   if not (fLurchTbl.BOF and fLurchTbl.EOF) then begin
  162.     { go to the first record }
  163.     fLurchTbl.First;
  164.     { run through all of the records and attempt to delete them }
  165.     for i := 0 to fLurchTbl.RecordCount-1 do begin
  166.       try
  167.         fLurchTbl.Delete;
  168.       except
  169.          { if the record is locked (i.e. if this is not a 'phantom' user but
  170.            rather an actual license seat being held)... }
  171.          on e: EDBEngineError do
  172.            if copy(e.message,1,30) <> 'Record locked by another user.' then raise
  173.            { ...then move on to the next record. }
  174.            else fLurchTbl.Next;
  175.       end; { try-except }
  176.     end; { for-do }
  177.   end; { if-then }
  178. end;
  179.  
  180. procedure TSeatChecker.SitDown;
  181. begin
  182.   { make sure we're not attempting to sit down in more than one seat }
  183.   if fStatus = sSitting then exit;
  184.   { make sure we've got a valid database.  You may want to do additional
  185.     checking here, to ensure that fDBName corresponds to a valid alias, using
  186.     Session.GetAliasNames. }
  187.   if fDBName = '' then raise ESeatCheckError.Create('No database specified.');
  188.   { Close the lurch TTable and set all of its properties }
  189.   fLurchTbl.Close;
  190.   fLurchTbl.DatabaseName := fDBName;
  191.   fLurchTbl.TableName := kLurchTableName;
  192.   { Open the lurch table and empty it of 'phantom' records (see EmptyLurchTable
  193.     or the associated documentation for more details). }
  194.   fLurchTbl.Open;
  195.   EmptyLurchTable;
  196.   { Now is where we check to see if another seat is available.  If the license
  197.     is based on concurrent users, then the total number of occupied seats must
  198.     be less than kMaxSeats, otherwise an exception is raised. }
  199.   case fMethod of
  200.     mByTotal : if fLurchTbl.RecordCount >= kMaxSeats then
  201.                  raise ESeatCheckError.Create('No seats available.');
  202.   { On the other hand, if the license is based on seats, then each user may
  203.     only be logged in one time.  Therefore, if the user attempting to log in
  204.     is already logged in, an exception is raised (see the note on licensing
  205.     at the bottom of this file, or check the associated documentation).  }
  206.     mByUser : if fLurchTbl.FindKey([fUserName]) then
  207.                  raise ESeatCheckError.Create('This userid already has a seat.');
  208.   end;
  209.   { If there is a seat available, reserve it.  TSeatChecker does this by
  210.     appending a new record to the table with the user's name, then editing that
  211.     record (and never posting).  This locks the record and prevents it from
  212.     being deleted by the EmptyLurchTable method above.
  213.     You may wish to modify this.  Primarily, you probably want to encrypt the
  214.     username string, so that it is more difficult for an end user to find a
  215.     way around TSeatChecker. }
  216.   fLurchTbl.Append;
  217.   fLurchTbl.FieldByName('Username').AsString := fUserName;
  218.   fLurchTbl.Post;
  219.   fLurchTbl.Edit;
  220.   { Finally, record the current status }
  221.   fStatus := sSitting;
  222. end;
  223.  
  224. procedure TSeatChecker.StandUp;
  225. begin
  226.   { begin by making sure we're not already standing }
  227.   if (fStatus = sStanding) then exit;
  228.   { also make sure that the lurch TTable is active and in edit state }
  229.   if (not fLurchTbl.Active) or
  230.      (fLurchTbl.state <> dsEdit) then raise ESeatCheckError.Create('Already standing up.');
  231.   { Cancel the edit, delete the record, and close the table. }
  232.   fLurchTbl.Cancel;
  233.   fLurchTbl.Delete;
  234.   fLurchTbl.Close;
  235.   { Finally, record the current status }
  236.   fStatus := sStanding;
  237. end;
  238.  
  239. end.
  240.  
  241. (*
  242. Notes on licensing:
  243. There are basically two ways of licensing software (over and above handing it
  244. out for free).  These are seat-based and concurrent use-based.
  245.  
  246. Seat-based licensing
  247. --------------------
  248. Seat-based licensing was quite popular until recently, when process servers
  249. began to catch on.  Seat-based licensing basically says that you pay $X in
  250. return for the right to install so-many seats.  If you buy one copy of
  251. Microsoft Word, you have the right to install it on one pc only.  This worked
  252. fine, until one day a little company in Florida called Citrix came out with
  253. a modified version of Windows NT called WinFrame, which allowed a single pc
  254. to act as a process server for multiple remote clients.  Now, a single copy of
  255. Microsoft Word installed on a single pc could now be used by the entire
  256. company at the same time.  It wasn't long before Microsoft realized the
  257. problem and rewrote their licensing agreement to grant a concurrent use-
  258. license.
  259. Seat-based licensing is not dead, however.  While new technology has rendered
  260. that particular type of seat-based licensing impotent, it is still possible
  261. to define a seat in such a way as to make an enforceable license.  If a seat is
  262. defined as a user of the system, then the license agreement merely states that
  263. no more than X userids may be used.  Then all the software need do is to
  264. 1.) Return an error when the system administrator attempts to set up more users
  265. than the license allows, and
  266. 2.) Return an error when the same userid attempts to log in multiple times.
  267. The second part of the above is where TSeatChecker comes in, by monitoring the
  268. users that are logged into the system and returning an error when a userid
  269. is 'recycled', or used more than once (presumably by more than one user).
  270.  
  271. Concurrent use-licensing
  272. ------------------------
  273. Concurrent use-based is the most common means of licensing software.  It
  274. means that you pay $X in return for the right to have so-many users using the
  275. system at the same time.  There are many different ways to enforce this type
  276. of licensing, from using dongles (which end-users hate) to using semaphores
  277. (which programmers hate).  TSeatChecker is a convenient alternative.  By
  278. keeping track of who is using the system, it can monitor the number of active
  279. users and return an error when a user attempts to use more sessions than are
  280. permissible in the licensing agreement.
  281.  
  282. One final note on security:
  283. No security system is perfect.  While TSeatChecker is a creative and effective
  284. way of controlling software piracy and enforcing licensing agreements, it is
  285. by no means the Holy Grail of security measures.  If anyone is clever enough
  286. and motivated enough, they will find a way through any system.  The key is to
  287. make sure the system is powerful enough to couter the expected level of
  288. cleverness and motivation in your end user.
  289. TSeatChecker is powerful enough to deter the average user with the average
  290. motivation to overcome their licensing agreement.  If you determine that your
  291. end user may likely have above-average motivation or cleverness, then you may
  292. wish to turn to something more sophisticated (like a dongle).
  293.